/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.checker;


import java.util.List;

import dex.compiler.checker.type.BooleanType;
import dex.compiler.checker.type.IntegerType;
import dex.compiler.checker.type.Type;
import dex.compiler.model.base.Identifiers;
import dex.compiler.model.base.Place;
import dex.compiler.model.statement.Assignment;
import dex.compiler.model.statement.Block;
import dex.compiler.model.statement.CallStatement;
import dex.compiler.model.statement.CatchBlock;
import dex.compiler.model.statement.Comment;
import dex.compiler.model.statement.Declaration;
import dex.compiler.model.statement.DecrementStatement;
import dex.compiler.model.statement.ForStatement;
import dex.compiler.model.statement.IfStatement;
import dex.compiler.model.statement.IncrementStatement;
import dex.compiler.model.statement.ReturnStatement;
import dex.compiler.model.statement.Statement;
import dex.compiler.model.statement.StatementVisitor;
import dex.compiler.model.statement.SwitchCase;
import dex.compiler.model.statement.SwitchStatement;
import dex.compiler.model.statement.ThrowStatement;
import dex.compiler.model.statement.TryStatement;
import dex.compiler.model.statement.WhileStatement;
import dex.compiler.problem.Crime;
import static dex.compiler.checker.StatementProblems.*;


/**
 * Checks statements for validity.
 */
public abstract class StatementChecker extends ExpressionChecker implements StatementVisitor {


	/**
	 * Constructs a new <code>StatementChecker</code>
	 * 
	 * @param config  the configuration for the checker
	 */
	public StatementChecker(CheckerConfig config) {
		super(config);
	}


	/**
	 * Returns the return type of the current function being checked.
	 * 
	 * @return  the return type
	 */
	protected abstract Type getCurrentReturnType();


	/**
	 * Checks the given assignment for validity.
	 *
	 * @param s  the statement to check
	 */
	public void visitAssignment(Assignment s) {
		Type lvalue = pop();
		Type rvalue = pop();
		if (flag()) return;
		
		if (!rvalue.canAssignTo(lvalue)) {
			reporter.error(BAD_ASSIGNMENT, s.getPlace(), rvalue.toString(), lvalue.toString());
		}
		
		if (s.getOperator() == null) {
			return; // Nothing left to do
		}
		
		InfixRules rules = new InfixRules(reporter, s.getPlace(), s.getOperator(), lvalue, rvalue);
		rules.check();
	}


	/**
	 * Checks the given block for validity.
	 *
	 * @param s  the statement to check
	 */
	public void enterBlock(Block s) {
		scope.enter();
	}


	/**
	 * Checks the given block for validity.
	 *
	 * @param s  the statement to check
	 */
	public void exitBlock(Block s) {
		scope.exit();

		boolean terminated = false;
		for (Statement a: s.getActions()) {
			if (terminated) {
				reporter.error(UNREACHABLE, a.getPlace(), "");
				return;
			}
			if (a.terminates()) {
				terminated = true;
			}
		}
	}


	/**
	 * Checks the given call statement for validity.
	 *
	 * @param s  the statement to check
	 */
	public void visitCallStatement(CallStatement s) {
		pop();
		flag();
	}


	/**
	 * Does nothing.
	 * 
	 * @param s  ignored
	 */
	public void visitComment(Comment s) {
	}


	/**
	 * Checks the given declaration for validity.
	 * 
	 * @param s  the statement to check
	 */
	public void visitDeclaration(Declaration s) {
		Type lvalue = manager.toType(unit, s.getType());
		if (s.getInitializer() != null) {
			Type rvalue = pop();
			if (flag()) return;
			if (!rvalue.canAssignTo(lvalue)) {
				reporter.error(BAD_INITIALIZER, s.getPlace(), rvalue.toString(), lvalue.toString());
			}
		}
		
		if (Identifiers.isQualified(s.getIdentifier())) {
			reporter.error(BAD_LOCAL, s.getPlace(), s.getIdentifier());
		}
		
		Type t = scope.getType(s.getIdentifier());
		if (t != null) {
			reporter.error(LOCAL_DUPE, s.getPlace(), s.getIdentifier());
		} else {
			scope.addLocalVariable(s.getIdentifier(), lvalue);
		}
	}

	
	void visitPostfix(Place place, Crime problem) {
		Type lvalue = pop();
		if (flag()) return;
		
		if (!(lvalue instanceof IntegerType)) {
			reporter.error(problem, place, lvalue.toString());
		}
	}
	
	
	/**
	 * Checks the given decrement statement for validity.
	 *
	 * @param s  the statement to check
	 */
	public void visitDecrementStatement(DecrementStatement s) {
		visitPostfix(s.getPlace(), BAD_DECREMENT);
	}

	
	/**
	 * Checks the given increment statement for validity.
	 *
	 * @param s  the statement to check
	 */
	public void visitIncrementStatement(IncrementStatement s) {
		visitPostfix(s.getPlace(), BAD_INCREMENT);
	}

	
	/**
	 * Checks the given for statement for validity.
	 * 
	 * @param s  the for statement to check
	 */
	public void enterForStatement(ForStatement s) {
		scope.enter();
	}


	/**
	 * Checks the given for statement for validity.
	 * 
	 * @param s  the for statement to check
	 */
	public void exitForStatement(ForStatement s) {
		scope.exit();
		if (s.getTest() != null) {
			Type condition = pop();
			if (condition != BooleanType.INSTANCE) {
				reporter.error(BAD_FOR, s.getTest().getPlace(), condition.toString()); 
			}
		}
	}


	/**
	 * Checks the given if statement for validity.
	 * 
	 * @param s  the if statement to check
	 */
	public void visitIfStatement(IfStatement s) {
		Type condition = pop();
		if (!(condition instanceof BooleanType)) {
			reporter.error(BAD_IF, s.getPlace(), condition.toString());
		}
	}
	
	
	/**
	 * Checks the given return statement for validity.
	 * 
	 * @param s  the statement to check
	 */
	public void visitReturnStatement(ReturnStatement s) {
		Type actual = null;
		if (s.getExpression() != null) {
			actual = pop();
			if (flag()) return;
		}
		
		Type formal = getCurrentReturnType();
		if ((actual == null) && (formal == null)) {
			return;
		}
		String aname = (actual == null) ? "void" : actual.toString();
		String fname = (formal == null) ? "void" : formal.toString();
		if (actual == null) {
			reporter.error(BAD_RETURN, s.getPlace(), aname, fname);
			return;
		}
		if (formal == null) {
			reporter.error(BAD_RETURN, s.getPlace(), aname, fname);
			return;
		}
		if (!actual.canAssignTo(formal)) {
			reporter.error(BAD_RETURN, s.getPlace(), aname, fname);
			return;
		}
	}

	
	/**
	 * Checks the given switch statement for validity.
	 * 
	 * @param s  the statement to check
	 */
	public void visitSwitchStatement(SwitchStatement s) {	
		Type test = pop();
		if (flag()) return;
		
		if (!(test instanceof IntegerType)) {
			reporter.error(BAD_SWITCH, s.getPlace(), test.toString());
		}
	}


	/**
	 * Checks the given switch case for validity.
	 * 
	 * @param s  the case to visit
	 */
	public void visitSwitchCase(SwitchCase s) {
		List<Type> cases = pop(s.getTests().size());
		if (flag()) return;
		
		for (Type t: cases) {
			if (!(t instanceof IntegerType)) {
				reporter.error(BAD_CASE, s.getPlace(), t.toString());
			}
		}
	}

	
	/**
	 * Checks the given throw statement for validity.
	 * 
	 * @param s  the statement to check
	 */
	public void visitThrowStatement(ThrowStatement s) {
		Type exception = pop();
		if (flag()) return;
		
		if (!exception.canAssignTo(manager.getBall())) {
			reporter.error(BAD_THROW, s.getPlace(), exception.toString());
		}
	}
	
	
	/**
	 * Checks the given catch block for validity.
	 * 
	 * @param c  the catch block to check
	 */
	public void visitCatchBlock(CatchBlock c) {
		Type exception = manager.toType(unit, c.getType());
		if (Identifiers.isQualified(c.getIdentifier())) {
			reporter.error(BAD_CATCH_NAME, c.getPlace(), c.getIdentifier());
		}
		if (!exception.canAssignTo(manager.getBall())) {
			reporter.error(BAD_CATCH, c.getPlace(), exception.toString());
		}
	}

	
	/**
	 * Checks the given while statement for validity.
	 * 
	 * @param s  the while statement to check
	 */
	public void visitTryStatement(TryStatement s) {
		// Nothing to do
	}


	/**
	 * Checks the given while statement for validity.
	 * 
	 * @param s  the while statement to check
	 */
	public void visitWhileStatement(WhileStatement s) {
		Type condition = pop();
		if (flag()) return;
		
		if (condition != BooleanType.INSTANCE) {
			reporter.error(BAD_WHILE, s.getPlace(), condition.toString());
		}
	}

}
